昨天我們建立好了環境和型別檔後,今天來繼續實作新增、刪除、修改 todo list 的功能
文章傳送門
🔗 Day27 - Vue3 + TS 實作簡易 To Do List (Part 1)
內容 | 重點摘要 |
---|---|
建立 store | import type |
建立元件 | Utility Type - Omit |
建議初步可以先把原本的寫法寫出來,再加上 ts 型別
完整 code
import { TodoStatus, type TodoStructure } from '@/types';
import { reactive, computed } from 'vue';
interface TodoStore {
[TodoStatus.Pending]: TodoStructure[];
[TodoStatus.InProgress]: TodoStructure[];
[TodoStatus.Completed]: TodoStructure[];
}
const defaultList = {
[TodoStatus.Pending]: [
// 先預設加入一個待辦
{
id: 1,
title: "寫鐵人賽",
description: "主題:實作",
status: TodoStatus.Pending,
},
],
[TodoStatus.InProgress]: [],
[TodoStatus.Completed]: [],
}
const todoStore = reactive<TodoStore>(defaultList);
// 取得
const getTodoByStatus = () => (status: TodoStatus) => {
return computed(() => todoStore[status]);
}
// 新增
const createTodo = (todo: TodoStructure) => {
todoStore[todo.status].push(todo);
}
// 刪除
const deleteTodo = (todo: TodoStructure) => {
todoStore[todo.status] = todoStore[todo.status].filter(item => item.id !== todo.id);
}
// 修改
const updateTodo = (todo: TodoStructure, newStatus: TodoStatus) => {
todo.status = newStatus;
}
export {
getTodoByStatus,
createTodo,
deleteTodo
}
在這段可以看到一個特殊語法 import type
這裡的 type 寫在 {}
裡面,雖然跟寫在外面(import type
)語法不同,但其實用意相同
import { TodoStatus, type TodoStructure } from '@/types';
Typescript 3.8 版本新增了 import type,使用 import type
的用意是更明確指出從某個模組導入的是型別(Type),而非值(Value)。這樣做有幾個主要好處:
清晰性
能夠清楚的表示導入的內容只用於靜態型別檢查,並不會被編譯成 JavaScript,讓維護和開發的人能更快速知道這些導入只用於 TypeScript 的型別系統中
提升編譯效率
TypeScript 編譯器知道 import type 只用於類型檢查,並不會被編譯成 JavaScript。避免了不必要的引入,有助於減少最終打包文件的大小,尤其是在大型專案中,每一點的效能提升都很寶貴呀~
避免名稱衝突
使用 import type 可以避免在導入型別時與局部變數或其他導入的值產生名稱衝突。減少了作用域污染的可能
想看 import 和 import type 的差別,可看 這篇文章
主要區分三個元件
這邊只著重寫 ts 有關的內容,想看完整程式碼點這
顧名思義,這個檔案就是在處理新增 todo 的功能啦~
<script setup lang="ts">
import { reactive, ref } from "vue";
import type { TodoStructure, TodoStatus } from "@/types";
import useTodos from "@/store/useTodos";
const shouldDisplayForm = ref(false);
const { createTodo } = useTodos();
// ✅ 關注這段
interface Props {
status: TodoStatus;
}
const props = defineProps<Props>();
// ✅ 關注這段
const newTodo = reactive<Omit<TodoStructure, "id">>({
title: "",
description: "",
status: props.status,
});
const resetForm = () => {
shouldDisplayForm.value = false;
newTodo.title = "";
newTodo.description = "";
};
const handleOnSubmit = () => {
createTodo({
id: Math.random() * 1000,
...newTodo,
});
resetForm();
};
</script>
其中這段使用到了 utility type
Omit
是一個工具型別(Utility Type),幫助我們在不修改原始的型別下,排除一些特定的屬性,建立一個新的型別
Omit 型別接收兩個參數:
第一個參數是傳入的 Type,是物件類型
第二個參數第二個參數是要忽略的鍵(key)
const newTodo = reactive<Omit<TodoStructure, "id">>({
title: "",
description: "",
status: props.status,
});
TodoGroup 的內容稍多,一樣只著重講 ts 部分,想看完整程式碼點這
首先因為每個待辦有拖曳功能,所以來安裝 Vue.Draggable 套件
<script setup lang="ts">
import { TodoStatus } from "@/types";
import Draggable from "vuedraggable";
import useTodos from "@/store/useTodos";
import CreateTodo from "@/components/CreateTodo.vue";
interface Props {
status: TodoStatus;
}
const props = defineProps<Props>();
const {
getTodoByStatus,
deleteTodo,
updateTodo
} = useTodos();
const todoList = getTodoByStatus(props.status);
const groupLabel = {
[TodoStatus.Pending]: "Pending",
[TodoStatus.InProgress]: "In Progress",
[TodoStatus.Completed]: "Completed",
};
const onDraggableChange = (payload: any) => {
if (payload?.added?.element?.status) {
updateTodo(payload?.added?.element, props.status);
}
};
</script>
TodoList
的完整內容都在下面,做的事很單純,只是引入 TodoGroup
,並帶入 status
Todo List 有三種狀態:未完成、進行中、已完成
<template>
<div class="groups-wrapper">
<TodoGroup :status="TodoStatus.Pending" />
<TodoGroup :status="TodoStatus.InProgress" />
<TodoGroup :status="TodoStatus.Completed" />
</div>
</template>
<script setup lang="ts">
import { TodoStatus } from "@/types";
import TodoGroup from "@/components/TodoGroup.vue";
</script>
github repo: https://github.com/hangineer/todo-app